/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { RuleListUtil } from "../../utils/common/DefectsList";
import { ArkFile, ts } from "arkanalyzer/lib";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects } from "../../model/Defects";
import { AstMatcher, MatcherCallback, MatcherTypes } from "../../matcher/Matchers";
import { Rule } from "../../model/Rule";
import { IssueReport } from '../../model/Defects';

export class NoConfusingNonNullAssertionCheck implements BaseChecker {
  public metaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: 'docs/no-confusing-non-null-assertion.md',
    description: 'Confusing combinations of non-null assertion and equal test like "a! == b", which looks very similar to not equal "a !== b',
  };
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];

  // 定义需要检测的目标运算符
  private readonly TARGET_OPERATORS = [
    ts.SyntaxKind.EqualsToken, 
    ts.SyntaxKind.EqualsEqualsToken,       
    ts.SyntaxKind.EqualsEqualsEqualsToken,  
  ];

  private buildMatcher: AstMatcher = {
    matcherType: MatcherTypes.AST,
    match(node: ts.Node): boolean {
        return ts.isBinaryExpression(node);
    }
  };

  public registerMatchers(): MatcherCallback[] {
    const matchBuildCb: MatcherCallback = {
        matcher: this.buildMatcher,
        callback: this.check
    };
    return [matchBuildCb];
  };

  public check = (node: ts.BinaryExpression, target: ArkFile) => {
    const nodeKind = node.operatorToken.kind;
    if (!this.TARGET_OPERATORS.includes(nodeKind)) {
      return;
    }

    const nonNullExpressionNodes = this.findAllNonNullExpressions(node.left);
    const hasMatchingEnd = nonNullExpressionNodes.some(
      expr => expr.getEnd() === node.left.getEnd()
    );

    if (!hasMatchingEnd) {
      return;
    }

    const sourceFile = node.getSourceFile();
    const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
    const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
    const message =
      nodeKind === ts.SyntaxKind.EqualsToken
        ? 'Confusing combination of non-null assertion and assignment like "a! = b", looks similar to "a != b".'
        : 'Confusing combination of non-null assertion and equality test like "a! == b", looks similar to "a !== b".';

    this.addIssueReport(start.line + 1, start.character + 1, end.character + 1, target.getFilePath(), message);
  }

  private findAllNonNullExpressions(node: ts.Node): ts.NonNullExpression[] {
    const results: ts.NonNullExpression[] = [];
    if (ts.isNonNullExpression(node)) {
        results.push(node);
    }
    for (const child of node.getChildren()) { //等号左边不一定是非空断言，1 + foo.num! == 2
        results.push(...this.findAllNonNullExpressions(child)); 
    }
    return results;
}

  private async addIssueReport(line: number, startCol: number, endCol: number, filePath: string, description: string) {
    const severity = this.rule.alert ?? this.metaData.severity;
    const defect = new Defects(line, startCol, endCol, description, severity,
      this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
    this.issues.push(new IssueReport(defect, undefined));
    RuleListUtil.push(defect);
  }
}
